#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Author: FQ
Module Description: lru_cache ... 加入zk开关，用于控制关闭使用缓存
File: lru_cache.py
Date: 2020/10/12
"""
import functools32
from thread import allocate_lock as Lock

from common.service_config import get_remote_service_switch
from pyshare.const.enums.remote_service_switch import ServiceSwitch
from pyshare.dal.tb_profile_register import TableModelMap
from common.service_config import get_server_id
from pyshare.utils.tb_profile_utils import TbProfileVerManage


def lru_cache(model=None, maxsize=None, is_cross=False):
    """Least-recently-used cache decorator.

    If *maxsize* is set to None, the LRU features are disabled and the cache
    can grow without bound.
    is_cross: 如果是True则表示，是跨服数据，使用缓存时，不需要从上下文获取区服信息

    Arguments to the cached function must be hashable.

    View the cache statistics named tuple (hits, misses, maxsize, currsize) with
    f.cache_info().  Clear the cache and statistics with f.cache_clear().
    Access the underlying function with f.__wrapped__.

    See:  http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used

    """
    # Users should only access the lru_cache through its public API:
    #       cache_info, cache_clear, and f.__wrapped__
    # The internals of the lru_cache are encapsulated for thread safety and
    # to allow the implementation to change (including a possible C version).

    def decorating_function(user_function,
                            tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
        if not model:
            raise Exception('use lru_cache must define model')

        hits, misses = [0], [0]
        kwd_mark = (object(),)          # separates positional and keyword args
        lock = Lock()                   # needed because OrderedDict isn't threadsafe

        if maxsize is None:
            cache = dict()              # simple cache without ordering or size limit

            @functools32.wraps(user_function)
            def wrapper(*args, **kwds):
                # 判断是否使用lru缓存
                if get_remote_service_switch(ServiceSwitch.OpenLruCache):
                    key = args
                    if kwds:
                        key += kwd_mark + tuple(sorted(kwds.items()))
                    if is_cross is False:
                        # 区服数据，需要区分区服
                        key += ('ServerId', get_server_id())
                    else:
                        key += ('ServerId', 'cross')

                    # 拼接版本号
                    key += ('ProfileVer', TbProfileVerManage.get_table_ver(model))
                    # 强制转换，参数中不支持list和dict
                    key = str(key)
                    try:
                        result = cache[key]
                        hits[0] += 1
                        return result
                    except KeyError:
                        pass
                    result = user_function(*args, **kwds)
                    cache[key] = result
                    misses[0] += 1
                    return result
                else:
                    return user_function(*args, **kwds)
        else:
            cache = functools32.OrderedDict()           # ordered least recent to most recent
            cache_popitem = cache.popitem
            cache_renew = cache.move_to_end

            @functools32.wraps(user_function)
            def wrapper(*args, **kwds):
                # 判断是否使用lru缓存
                if get_remote_service_switch(ServiceSwitch.OpenLruCache):
                    key = args
                    if kwds:
                        key += kwd_mark + tuple(sorted(kwds.items()))

                    if is_cross is False:
                        # 区服数据，需要区分区服
                        key += ('ServerId', get_server_id())
                    else:
                        key += ('ServerId', 'cross')

                    # 拼接配置表版本号
                    key += ('ProfileVer', TbProfileVerManage.get_table_ver(model))
                    # 强制转换，参数中不支持list和dict
                    key = str(key)
                    with lock:
                        try:
                            result = cache[key]
                            cache_renew(key)    # record recent use of this key
                            hits[0] += 1
                            return result
                        except KeyError:
                            pass
                    result = user_function(*args, **kwds)
                    with lock:
                        cache[key] = result     # record recent use of this key
                        misses[0] += 1
                        if len(cache) > maxsize:
                            cache_popitem(0)    # purge least recently used cache entry
                    return result
                else:
                    return user_function(*args, **kwds)

        def cache_info():
            """Report cache statistics"""
            with lock:
                return functools32._CacheInfo(hits[0], misses[0], maxsize, len(cache))

        def cache_clear():
            """Clear the cache and cache statistics"""
            with lock:
                cache.clear()
                hits[0] = misses[0] = 0

        wrapper.cache_info = cache_info
        wrapper.cache_clear = cache_clear
        return wrapper

    return decorating_function


def register_for_table_ver():
    """
    tb配置表版本自动维护，注册函数
    :return:
    """
    def decorator(cls):
        TableModelMap.map_target(cls.__name__)
        return cls
    return decorator
